package com.tledu.zyf.util;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AuthUtil {
	// 存储实例化之后的对象
	private Map<String, Object> objects = null;

	/**
	 * 对外提供一个根据名字获取对象的方法
	 * 
	 * @param key
	 * @return
	 */
	public Object getBean(String key) {
		return objects.get(key);
	}

	// 单例模式
	private static AuthUtil authUtil = null;

	public static AuthUtil getInstance() {
		if (authUtil == null) {
			authUtil = new AuthUtil();
		}
		return authUtil;
	}

	private AuthUtil() {
		// 获取设置的包名
		String packageName = PropertiesUtil.getDaoProperties().getProperty("path");
		try {
			objects = new HashMap<String, Object>();
			// 根据路径 获取class
			getClassByPackage(packageName);
			// System.out.println(packageName);

			// 遍历所有类全名
			for (String allName : classfiles) {
				// 获取运行时类
				Class clzObj = Class.forName(allName);
				if (!clzObj.isAnnotationPresent(New.class)) {
					continue;
				}
				// 能到这里 说明加着@New注解了
				// 获取@New传递的value值
				New newClass = (New) clzObj.getAnnotation(New.class);
				String key = newClass.value();
				// System.out.println(key);

				// 判断是否传递value值
				if (key.trim().equals("")) {
					// 如果没有传递value 就默认使用类名首字母小写
					// System.out.println(clzObj.getSimpleName());

					// 获取简称
					key = clzObj.getSimpleName();
					// System.out.println(key.substring(0, 1).toLowerCase());
					// System.out.println(key.substring(1));
					key = key.substring(0, 1).toLowerCase() + key.substring(1);
				}
				// System.out.println(key);

				// 创建了该类的对象
				Object object = clzObj.newInstance();
				objects.put(key, object);

			}

			// 判断对象方法是否有@Di,并判断是否对value传递数据
			for (String key : objects.keySet()) {
				// 遍历map中所有对象,并获取运行时类
				Object valueObject = objects.get(key);
				// 获取运行时类
				Class clz = valueObject.getClass();
				// 根据运行时类获取所有的方法
				Method[] moMethods = clz.getMethods();
				for (Method method : moMethods) {
					// 遍历所有的方法,判断每个方法上是否加着@Di注解,如果没有加着@Di就跳过
					if (!method.isAnnotationPresent(Di.class)) {
						continue;
					}
					// 如果加着@Di,就需要获取对应的value值
					// 到这里说明一定有加着 DI
					// 就获取传递的value 值
					Di di = method.getAnnotation(Di.class);
					String value = di.value();
					// 如果没有value 值,就截取方法首字母小写
					if (value.trim().equals("")) {
						// 截取方法名 如 UserDao
						value = method.getName().substring(3);
						// 首字母小写
						char c = value.charAt(0);
						// 判断首字母是不是大写
						if (c >= 65 && c <= 97) {
							c += 32;
						}
						value = c + value.substring(1);
					}

					// System.out.println(value);
					// 然后根据这个 value ,作为key去map中找是否有这个对象

					Object object = objects.get(value);
					/**
					 * 
					 * 如果 object 为空,说明根据名字没有找到对象
					 * 
					 * 就需要更改为对应的类型匹配
					 * 
					 */
					if (object == null) {
						// 如果没有找到,就获取该方法的参数列表的数据类型
						// 参数列表
						Class[] parameterTypes = method.getParameterTypes();
						// 遍历参数列表
						for (Class parameterType : parameterTypes) {
//							System.out.println(parameterType);
							// 然后遍历map中所有的对象,并获取每个对象的父接口类型
							for (String key1 : objects.keySet()) {
								// 获取map中的对象
								Object valueObject1 = objects.get(key1);
								// 获取运行时类
								Class clazz = valueObject1.getClass();
								// 获取每个对象的父接口类型
								Class interfaces[] = clazz.getInterfaces();
								// 遍历父接口类型
								for (Class interfaceClass : interfaces) {
									// 两个类型进行比较,如果类型一致,那么说明该对象,就是需要进行注入的这个数据
//									System.out.println(parameterType);
//									System.out.println(interfaceObj);
									if (parameterType.equals(interfaceClass)) {
										// 调用方法,进行注入
//										System.out.println(valueObject.toString());
//										System.out.println(valueObject1.toString());
										// com.tledu.zyf.service.impl.UserService@6e0be858
										// com.tledu.zyf.dao.impl.UserDao@61bbe9ba
										// valueObject : 是方法所在类对象
										// valueObject1 : 是参数
										method.invoke(valueObject, valueObject1);
									}
								}
							}
						}

						// 如果还是没有找到,就说明没有
					} else {
						// 到这里说明能够找到对象
						// 调用方法, 进行注入
						method.invoke(valueObject, object);
						/**
						 * 1 方法对象 method
						 * 
						 * 2 方法所在类对象 valueObj
						 * 
						 * 3 参数 object
						 * 
						 * clzObj 当前方法所在运行时类
						 * 
						 * objects 容器
						 */
					}

				}

				// @Value 注解注入
				// 判断对象属性是否有@Value,并判断是否对value传递数据
				for (String keyByValue : objects.keySet()) {
					// 遍历map中所有对象,并获取运行时类
					Object valueObjectByValue = objects.get(key);
					// 获取运行时类
					Class clzByValue = valueObjectByValue.getClass();
					// 根据运行时类获取所有的属性
					Field[] fields = clzByValue.getDeclaredFields();
					for (Field field : fields) {
//						System.out.println(field);
						// 遍历所有的属性,判断每个属性上是否加着@Value注解,如果没有加着@Value就跳过
						if (!field.isAnnotationPresent(Value.class)) {
							continue;
						}
						// 如果加着@Value,就需要获取对应的value值
						// 到这里说明一定有加着 Value
						// 就获取传递的value 值
						Value valueV = field.getAnnotation(Value.class);
						String value = valueV.value();
						// 用来存储类型转换之后的变量值
						Object object = null;
						// value 值不为空,就注入对应的属性值
						if (value == null) {
							// value值为空就注入变量类型对应的默认值
							object = getDefaultValue(field.getType().getSimpleName());
						}
						// 打破封装性,否则不能操作私有化变量,设置为 true 之后,在访问的时候就不会再检查访问权限
						field.setAccessible(true);
//							System.out.println(field.getType().getSimpleName());
						// 将 value 按数据类型进行转换
						object = getValue(field.getType().getSimpleName(), value);
						// 注入变量
						field.set(valueObjectByValue, object);
						/**
						 * 变量对象 .set (变量所在对象,值)
						 * 
						 * 1 变量对象 : field
						 * 
						 * 2 变量所在对象 : valueObjectByValue
						 * 
						 * 3 转换后的值 : object
						 */
//						System.out.println(valueObjectByValue + "[[");
//						System.out.println(valueObject + ";;");
//						System.out.println(object + "]]");

					}

				}
				System.out.println(valueObject);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	/**
	 * 保存包下的所有类全名
	 */
	private List<String> classfiles = new ArrayList<String>();

	/**
	 * 通过包路径 递归获取包下所有的类
	 * 
	 * @param packageName
	 */
	private void getClassByPackage(String packageName) {
		// 把.换成/
		String pr = packageName.replace(".", "/");
		// 根据pr 获取对应的全路径 D://xxxxx/xxxx
		String packagePath = AuthUtil.class.getClassLoader().getResource(pr).getPath();

		// 获取对应的文件对象
		File file = new File(packagePath);
		// 获取包下所有子文件
		File[] files = file.listFiles();
		// 遍历每个对象
		for (File subFile : files) {
			// 判断是否是文件
			if (subFile.isFile()) {
				// 判断是否是class文件
				if (subFile.getName().endsWith(".class")) {
					String className = subFile.getName();
					// 因为最终Class.forName("xxx") 需要传入的是类全名,但是没有.class
					// 所以 需要截取字符串
					// lastIndexOf 最后一次出现的下标
					String shortName = className.substring(0, className.lastIndexOf("."));

					// 反射要用类全名 所以需要拼接
					// System.out.println(packageName+"."+shortName);

					// 把类全名保存到list中
					classfiles.add(packageName + "." + shortName);
				}
			} else {
				// 递归遍历
				getClassByPackage(packageName + "." + subFile.getName());
			}
		}
	}

	/**
	 * 字符串转化为对应参数str传入的数据类型
	 * 
	 * 有两种方法 :
	 * 
	 * 1 调用包装类自身的构造器
	 * 
	 * new 包装类 (字符串)
	 * 
	 * 2 数值类型的包装类和引用类型的包装类都有一个对应的转换字符串方法
	 * 
	 * 也就是说 : 数值类型包装类 . parse数值类型(字符串)
	 * 
	 * 如果是返回 char 类型的值 , 也可以自己返回字符串中对应的值
	 * 
	 * @param str
	 * @param value
	 * @return
	 */
	private Object getValue(String str, String value) {
		switch (str) {
		case "char":
//			System.out.println(value.charAt(0));
			// 默认返回字符串的第一个数值为char的值
			return value.charAt(0);
//			return Character.par
		case "boolean":

//			return new Boolean(value);
			return Boolean.parseBoolean(value);
		case "byte":

			return new Byte(value);
		case "short":

//			return new Short(value);
			return Short.parseShort(value);
		case "int":

			return Integer.parseInt(value);
		case "long":

//			return new Long(value);
			return Long.parseLong(value);
		case "float":

//			return new Float(value);
			return Float.parseFloat(value);
		case "double":

//			return new Double(value);
			return Double.parseDouble(value);
		default:
			// 默认返回字符串
			return value;
		}
	}

	/**
	 * 返回一个 str 对应的数据类型的默认值
	 * 
	 * 比如 int : 返回 0
	 * 
	 * String : 返回 null
	 * 
	 * @param str
	 * @return
	 */
	private Object getDefaultValue(String str) {
		switch (str) {
		case "char":

			return '\u0000';
		case "boolean":

			return false;
		case "byte":

			return 0;
		case "short":

			return 0;
		case "int":

			return 0;
		case "long":

			return 0;
		case "float":

			return 0.0;
		case "double":

			return 00.00;
		default:
			return null;
		}
	}
}
